package com.passion.aspects;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.passion.annotation.Limit;
import com.passion.common.exception.MyException;
import com.passion.common.util.FNV;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 类描述：限制重复操作注解切面
 *
 * @author ZhuYin
 * @since 2024年07月19日
 */
@Slf4j
@Aspect
@Component
public class LimitAspect {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 切入点定义：指明要在什么样的条件下才能被触发
     */
    @Pointcut("@annotation(com.passion.annotation.Limit)")
    public void pointCut() {
    }

    /**
     * 环绕通知，围绕着方法执行
     * @param pjp 切点
     * @return object
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String sessionId = Objects.requireNonNull(RequestContextHolder.getRequestAttributes()).getSessionId();
        assert attributes != null;
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 被拦截的方法
        Method method = signature.getMethod();
        // 注解信息
        Limit limit = method.getAnnotation(Limit.class);
        int lockedTime = limit.lockedTime();
        TimeUnit timeUnit = limit.timeUnit();
        HttpServletRequest request = attributes.getRequest();
        // 获取请求参数
        Object[] args = pjp.getArgs();
        String key = sessionId + "-" + request.getServletPath();
        if (args != null && args.length > 0) {
            // 对于有参数的方法，则限定时间内同一个人使用相同参数调用才认为是重复
            try {
                key += ("-" + FNV.pkID(JSON.toJSONString(args)));
            } catch (Exception e) {
                log.error("json化参数异常：", e);
            }
        }
        // 如果缓存中有这个url视为重复提交
        if (redisTemplate.opsForValue().get(key) == null) {
            Object o = pjp.proceed();
            redisTemplate.opsForValue().set(key, request.getRequestURI());
            log.info("请求的sessionId：{}，请求URI：{}", key, request.getRequestURI());
            redisTemplate.expire(key, lockedTime, timeUnit);
            return o;
        } else {
            String loginId = "";
            try {
                loginId = StpUtil.getLoginIdAsString();
            } catch (Exception e) {

            }
            log.error("[userId={}]重复操作：{}", loginId, request.getServletPath());
            String timeUnitDesc = TimeUnit.SECONDS.equals(timeUnit) ? "秒"
                    : TimeUnit.MINUTES.equals(timeUnit) ? "分钟"
                    : TimeUnit.HOURS.equals(timeUnit) ? "小时"
                    : TimeUnit.DAYS.equals(timeUnit) ? "天"
                    : TimeUnit.MILLISECONDS.equals(timeUnit) ? "毫秒"
                    : TimeUnit.MICROSECONDS.equals(timeUnit) ? "微秒"
                    : TimeUnit.NANOSECONDS.equals(timeUnit) ? "纳秒"
                    : "";
            throw new MyException(lockedTime + timeUnitDesc + "内禁止重复操作");
        }
    }
}
